package com.fengxing.ams.pub.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.fengxing.ams.pub.AMSException;

/**
 * A helper class to read and parse property configuration file.
 * @author draco.yang
 * @date 08-17-2011
 */
public class ConfigurationReader {
    /**
     * A static log4j logger.
     */
    private static Logger logger = Logger.getLogger(ConfigurationReader.class);
    
    /**
     * The property map parsed from configuration file,
     * key is property name and value is property value.
     */
    private Map<String, String> propertyMap = new HashMap<String, String>();
    
    /**
     * The property names that support pattern matches.
     */
    private List<String> patternPropertyNames = new ArrayList<String>();
    
    /**
     * A path matcher utility object used to match configuration value with ant path pattern.
     */
    private IPathMatcher pathMatcher = new AntPathMatcher();
    
    /**
     * Default constructor with configuration file path, the properties in the
     * file will be parsed in the constructor. 
     * @param file the configuration file path.
     */
    public ConfigurationReader(String file) {
        initializeConfigurations(file);
    }
    
    /**
     * Read and parse configurations from given file.
     * @param file the configuration file path.
     */
    private void initializeConfigurations(String file) {
        // find configuration file
        InputStream is = ConfigurationReader.class.getClassLoader().getResourceAsStream(file);
        if (is == null) {
            throw new AMSException("Can not find configuration file " + file);
        }
        if (logger.isDebugEnabled()) {
            URL url = ConfigurationReader.class.getClassLoader().getResource(file);
            logger.debug("Configuration file path:" + url);
        }
        
        // load properties
        try {
            Properties props = new Properties();
            props.load(is);
            for (String name : props.stringPropertyNames()) {
                propertyMap.put(name, props.getProperty(name));
                if (pathMatcher.isPattern(name)) {
                    patternPropertyNames.add(name);
                }
            }
        }catch (IOException e) {
            throw new AMSException("Read configuration file " + file + " failed", e);
        }
    }
    
    /**
     * Return property value by name, if the property is not configured,
     * then null will be returned.
     * @param name the property name.
     * @return the property value, null if it does not exist.
     */
    public String get(String name) {
        String value = propertyMap.get(name);
        if (value == null) {
            value = getValueWithPatternMatch(name);
        }
        return value;
    }
    
    /**
     * Return property value by name, if the property is not configured,
     * then the default value will be returned.
     * @param name the property name.
     * @param defaultValue the default value that will be returned when
     *      the specified property is not configured.
     * @return the property value, the default value if the specified property
     *      does not exist.
     */
    public String get(String name, String defaultValue) {
        String value = get(name);
        if (value != null) {
            return value;
        }
        return defaultValue;
    }
    
    /**
     * Return the property value by name and convert it to integer. if the
     * property is not configured or it's empty, then null will be returned.
     * @param name the property name.
     * @return the configured integer value, null if it does not exist or it's empty.
     */
    public Integer getInt(String name) {
        String value = get(name);
        if (StringUtils.isNotBlank(value)) {
            return Integer.valueOf(value);
        }
        return null;
    }
    
    /**
     * Return property value by name and convert it to integer, if the
     * property is not configured or it's empty, then the default value
     * will be returned.
     * @param name the property name.
     * @param defaultValue default integer value that will be returned
     *      when the property is not configured or it's empty.
     * @return the configured integer value, default value if it does not
     *      exist or it's empty.
     */
    public Integer getInt(String name, Integer defaultValue) {
        Integer value = getInt(name);
        if (value != null) {
            return value;
        }
        return defaultValue;
    }
    
    /**
     * Return the property value by name and convert it to boolean. if the
     * property is not configured or it's empty, then null will be returned.
     * @param name the property name.
     * @return the configured boolean value, null if it does not exist or it's empty.
     */
    public Boolean getBoolean(String name) {
        String value = get(name);
        if (StringUtils.isNotBlank(value)) {
            if ("on".equalsIgnoreCase(value)) {
                return Boolean.TRUE;
            } else if ("off".equalsIgnoreCase(value)) {
                return Boolean.FALSE;
            }
            return Boolean.valueOf(value);
        }
        return null;
    }
    
    /**
     * Return property value by name and convert it to boolean, if the
     * property is not configured or it's empty, then the default value
     * will be returned.
     * @param name the property name.
     * @param defaultValue default boolean value that will be returned
     *      when the property is not configured or it's empty.
     * @return the configured boolean value, default value if it does not
     *      exist or it's empty.
     */
    public Boolean getBoolean(String name, Boolean defaultValue) {
        Boolean value = getBoolean(name);
        if (value != null) {
            return value;
        }
        return defaultValue;
    }
    
    /**
     * Return all the property values.
     * @return a list of property values.
     */
    public List<String> getProperties() {
        return new ArrayList<String>(propertyMap.values());
    }
    
    /**
     * Return all the property names.
     * @return a list of property names.
     */
    public List<String> getPropertyNames() {
        return new ArrayList<String>(propertyMap.keySet());
    }
    
    /**
     * Get the value of a pattern property if the given name matches.
     * @param name
     * @return the value if matched, otherwise null. 
     */
    private String getValueWithPatternMatch(String name) {
        for (String patternPropertyName : patternPropertyNames) {
            if (pathMatcher.match(patternPropertyName, name)) {
                return propertyMap.get(patternPropertyName);
            }
        }
        return null;
    }
}
